﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Audit;
using BackOffice.Models;
using DataLayer;
using System.Globalization;
using Product = DataLayer.Product;


namespace BackOffice
{
    public class BackOffice : IBackOfficeForAudit
    {
        private Dictionary<int, List<Order>> dictDayToOrders = new Dictionary<int, List<Order>>();
        private const int MaxOrdersHours = 8;
        private Dictionary<int, int> delays = new Dictionary<int, int>();
        private DateTime currentDate;

        public BackOffice()
        {
            currentDate = DateTime.Now;
        }

        public int ProccessDay = 1;

        public void ProccessOrder(int day)
        {
            IList<Order> orders = Repository.Instance.GetOrdersByDate(currentDate.AddDays(ProccessDay-1))
                .Where(item => item.Status != (byte)OrderStatus.InProcess).ToList();
            
            if (orders.Count == 0)
            {
                delays.Add(day, MaxOrdersHours);
                ProccessDay++;
                return;
            }

            bool enougthMaterials = true;
            foreach (Order order in orders)
            {
                if (CheckMaterials(order))
                {
                    order.Date.CompleteDate = currentDate.AddDays(day);
                    order.Status = (byte)OrderStatus.InProcess;
                    Repository.Instance.SubstructMaterials(order);
                }
                else
                {
                    enougthMaterials = false;
                }
            }

            delays.Add(day, GetDelay(orders.Where(item => item.Status == (byte)OrderStatus.InProcess).ToList()));
            if (enougthMaterials)
                ProccessDay++;

        }

        public MaterialOrder GetMaterialOrder(int day)
        {
            return GenerateMaterialOrder(day);
        }

        public bool IsOrderCanModify(Order order)
        {
            return order.Status != (byte)OrderStatus.InProcess;
        }

        public void CalculateOrderCompletedDate(ref Order order, int day)
        {
            int index = day;

            if (dictDayToOrders.Count == 0 || !dictDayToOrders.ContainsKey(index))
                dictDayToOrders.Add(index, new List<Order>());

            IEnumerable<DataLayer.Product> products = Repository.Instance.GetProductsByOrder(order);
            double orderTimeToMake = products.Sum(item => item.TimeToMake);
            bool isOrderDelete = true;

            if (order.OrderId != 0)
            {
                int dayOfOrder = (order.Date.ExecutionDate.Value.Date - currentDate.Date).Days;
                if (!dictDayToOrders.ContainsKey(dayOfOrder) || dictDayToOrders[dayOfOrder].Count < 1)
                    throw new ArgumentException("День заказа при его изменении ищется неверно");

                if (!dictDayToOrders[dayOfOrder].Contains(order))
                    throw new ArgumentException("Заказ в словаре не найден.");

                //проверяем что изменённый заказ может остаться на своём старом месте, только изменив свои товары
                if (GetTimeToMakeForOrders(dictDayToOrders[dayOfOrder]) <= MaxOrdersHours)
                {
                    isOrderDelete = false;
                }
            }

            if (isOrderDelete && dictDayToOrders[index].Count > 0
                && orderTimeToMake > MaxOrdersHours - GetTimeToMakeForOrders(dictDayToOrders[index]))
            {
                bool isFound = false;
                while (!isFound)
                {
                    index++;
                    if (!dictDayToOrders.ContainsKey(index))
                        dictDayToOrders.Add(index, new List<Order>());

                    if (dictDayToOrders[index].Count == 0 ||
                        MaxOrdersHours - GetTimeToMakeForOrders(dictDayToOrders[index]) <= orderTimeToMake)
                    {
                        isFound = true;
                    }
                }
            }

            if (order.OrderId == 0)
            {
                order.Status = (byte) OrderStatus.Created;
                order.IsNotified = false;
                order.IsUrgent = false;
                order.IsDeleted = false;
            }
            else if(isOrderDelete)
            {
                dictDayToOrders[index].Remove(order);
            }

            if (isOrderDelete)
            {
                order.Date.ExecutionDate = DateTime.Now.AddDays(index);
                dictDayToOrders[index].Add(order);
            }

            Repository.Instance.SubmitChanges();
        }

        public void ApproveOrder(ref Order order, int day)
        {
            int index = (order.Date.ExecutionDate.Value.Date - currentDate.Date).Days;
            if (order.IsDeleted)
            {
                dictDayToOrders[index].Remove(order);
                if (order.OrderId != 0)
                    Repository.Instance.DeleteOrder(order);
            }
            else
            {
                order.Date.PredictedDate = order.Date.ExecutionDate.Value.AddDays(order.IsUrgent ? 5 : 14);
                order.Date.UpdatedDate = currentDate.AddDays(day);
                
                if (order.OrderId == 0)
                    Repository.Instance.InsertOrder(order);
            }

            Repository.Instance.SubmitChanges();
        }

        private bool CheckMaterials(Order order)
        {
            Dictionary<int, int> orderMaterialsCount = Repository.Instance.GetCountMaterialsForOrder(order);
            IList<Material> allMaterials = Repository.Instance.GetAllMaterials();
            return orderMaterialsCount.All(pair => allMaterials
                                            .Single(item => item.MaterialId == pair.Key)
                                            .QuantityInStock >= pair.Value
                                           );
        }

        private static double GetTimeToMakeForOrders(IEnumerable<Order> orders)
        {
            return orders.Sum(item => Repository.Instance.GetProductsByOrder(item).Sum(summer => summer.TimeToMake));
        }

        private static Dictionary<int, int> MaterialToTotalAmount(IEnumerable<ProductMaterial> productMaterials)
        {
            var materialToTotalAmount = new Dictionary<int, int>();
            foreach (var item in productMaterials)
            {
                if (!materialToTotalAmount.ContainsKey(item.MaterialId))
                    materialToTotalAmount.Add(item.MaterialId, 0);
                materialToTotalAmount[item.MaterialId] += item.Quantity;
            }
            return materialToTotalAmount;
        }

        private MaterialOrder GenerateMaterialOrder(int day)
        {
            IList<Order> orders = Repository.Instance.GetOrderForLastWeek(day);
            var orderMaterials = new Dictionary<int, int>();
            var availableMaterials = Repository.Instance.GetAllMaterials().ToDictionary(key => key.MaterialId, value => value.QuantityInStock);

            foreach (Order order in orders)
            {
                IEnumerable<ProductMaterial> materialsForThisOrder = Repository.Instance.GetProductMaterialsForOrder(order);
                Dictionary<int, int> countMaterialForOrder = MaterialToTotalAmount(materialsForThisOrder);

                foreach (int key in countMaterialForOrder.Keys)
                {
                    if (!orderMaterials.ContainsKey(key))
                        orderMaterials.Add(key, 0);
                    orderMaterials[key] += countMaterialForOrder[key];
                }
            }

            var materialOrder = new MaterialOrder();
            materialOrder.ArrivalDate = currentDate.AddDays(day + 3);
            materialOrder.OrderDate = DateTime.Now;
            materialOrder.Status = (byte)MaterialStatus.WaitingForDelivery;

            int[] keys = new int[orderMaterials.Keys.Count]; 
            orderMaterials.Keys.CopyTo(keys,0);
            foreach (int material in keys)
            {
                if (availableMaterials[material] >= orderMaterials[material])
                    orderMaterials[material] = 0;
                else
                    orderMaterials[material] -= availableMaterials[material];

                orderMaterials[material] += Convert.ToInt32(orderMaterials[material] * 0.5);

                MaterialOrderDetail orderDetail = new MaterialOrderDetail();
                orderDetail.MaterialId = material;
                orderDetail.Quantity = orderMaterials[material];
                materialOrder.MaterialOrderDetails.Add(orderDetail);
            }

            if (!orderMaterials.Values.Any(item => item != 0))
                return null;

            Repository.Instance.InsertMaterialOrder(materialOrder);
            return materialOrder;
        }

        //private static Dictionary<int, int> MaterialToTotalAmount(IEnumerable<ProductMaterial> productMaterials)
        //{
        //    var materialToTotalAmount = new Dictionary<int, int>();
        //    foreach (var item in productMaterials)
        //    {
        //        if (!materialToTotalAmount.ContainsKey(item.MaterialId))
        //            materialToTotalAmount.Add(item.MaterialId, 0);
        //        materialToTotalAmount[item.MaterialId] += item.Quantity;
        //    }
        //    return materialToTotalAmount;
        //}

        //private MaterialOrder GenerateMaterialOrder(int day)
        //{
        //    //IList<Order> orders = Repository.Instance.GetOrderForLastWeek(day);
        //    var materials = new Dictionary<int, int>();
            
        //    //получаем общее кол-во для каждого материала
        //    Dictionary<int, int> allMaterialOrders = Repository.Instance.GetAllMaterialOrders();
        //    var sum = 0; // сумма кол-ва всех использованных материалов

        //    foreach (var key in allMaterialOrders.Keys)
        //    {
        //        sum += allMaterialOrders[key];
        //    }

        //    Dictionary<int, double> factor = new Dictionary<int, double>();

        //    foreach (var key in allMaterialOrders.Keys)
        //    {
        //        factor.Add(key, (double)((double)allMaterialOrders[key] / (double)sum));
        //    }

        //    Dictionary<int, int> lastWeekMaterialsInStock = Repository.Instance.GetLastWeekMaterialsInStock(day);

        //    foreach (Order order in dictDayToOrders[day])
        //    {
        //        IEnumerable<ProductMaterial> materialsForThisOrder = Repository.Instance.GetProductMaterialsForOrder(order);
        //        Dictionary<int, int> countMaterialForOrder = MaterialToTotalAmount(materialsForThisOrder);

        //        foreach (int key in countMaterialForOrder.Keys)
        //        {
        //            if (!materials.ContainsKey(key))
        //                materials.Add(key, 0);
        //            materials[key] += countMaterialForOrder[key] + (int)factor[key] * countMaterialForOrder[key] - (lastWeekMaterialsInStock.ContainsKey(key) ? lastWeekMaterialsInStock[key] : 0);
        //        }
        //    }

        //    var materialOrder = new MaterialOrder();
        //    materialOrder.ArrivalDate = currentDate.AddDays(day+3);
        //    materialOrder.OrderDate = DateTime.Now;
        //    materialOrder.Status = (byte) MaterialStatus.WaitingForDelivery;
            
        //    foreach (int material in materials.Keys)
        //    {
        //        MaterialOrderDetail orderDetail = new MaterialOrderDetail();
        //        orderDetail.MaterialId = material;
        //        orderDetail.Quantity = materials[material];
        //        materialOrder.MaterialOrderDetails.Add(orderDetail);
        //    }

        //    Repository.Instance.InsertMaterialOrder(materialOrder);
        //    return materialOrder;
        //}

        public Dictionary<int, int> GetDaysDelays()
        {
            return delays;
        }

        private int GetDelay(IEnumerable<Order> orders)
        {
            double freeDayTime = (MaxOrdersHours - GetTimeToMakeForOrders(orders));
            return (int)freeDayTime;
        }
    }
}
